/*
 * @Description:
 * @Author: fhw
 * @Date: 2022-12-30 10:43:06
 */
import { User } from '@/interface'
import { defineComponent, onMounted, PropType, reactive, ref, watch } from 'vue'
import { FormEmotion } from '@/components'
import { PictureOutlined, VideoCameraOutlined, PhoneOutlined } from '@ant-design/icons-vue'
import { isNil } from 'ramda'
import { message, Modal } from 'ant-design-vue'
import AtDialog from './components/AtDialog'
import './style.scss'
const styleRel = /class\s*?=\s*?([‘"])[\s\S]*?\1/gi
const classRel = /class\s*?=\s*?([‘"])[\s\S]*?\1/gi
const props = {
	content: [String, Number] as PropType<string | number>,
	height: Number as PropType<number>,
	width: Number as PropType<number>,
	imgLoading: {
		type: Boolean as PropType<boolean>,
		default: false,
	},
	placeholder: {
		type: String as PropType<string>,
		default: '请输入',
	},
	// 是否显示表情
	showEmotion: {
		type: Boolean as PropType<boolean>,
		default: false,
	},
	// 是否显示发送图片
	showImg: {
		type: Boolean as PropType<boolean>,
		default: false,
	},
	// 是否显示发送图片
	showVoice: {
		type: Boolean as PropType<boolean>,
		default: false,
	},
	// 是否显示发送图片
	showVideo: {
		type: Boolean as PropType<boolean>,
		default: false,
	},
	showSend: {
		type: Boolean as PropType<boolean>,
		default: true,
	},
	direction: {
		type: String as PropType<'top' | 'bottom'>,
		default: 'top',
	},
	// 输入框内容变化时的回调
	onChange: Function as PropType<(string: string) => void>,
	// 发送
	onSend: Function as PropType<(string: string) => void>,
	// 发送图片回调
	onSendImg: Function as PropType<(object: any) => void>,
	// 发送图片给具体用户
	sendImgUserInfo: Object as PropType<User>,
	// 显示@用户数据
	mockData: Array,
} as const
export default defineComponent({
	props,
	emits: ['update:content', 'openVideo'],
	setup(props, { emit, slots }) {
		onMounted(() => {
			setCursor(divRef.value as HTMLElement)
		})
		const pageState = reactive({
			node: null as any, // 获取到节点
			user: '', // 选中项的内容
			endIndex: 0 as number | undefined, // 光标最后停留位置
			queryString: '', // 输入数据
			showDialog: false, // 是否显示弹窗
			position: {
				x: 0 as number,
				y: 0 as number,
			}, // 弹窗显示位置
		})
		// 输入框ref
		const divRef = ref<HTMLElement>()
		// 表情包添加
		function onSendEmotion(imgSrc: string) {
			const spanHtml = window.document.createElement('span')
			spanHtml.innerHTML = imgSrc
			const documents = document as any
			if (documents.selection) {
				divRef.value?.focus()
				const sel = documents.selection.createRange()
				sel.text = spanHtml
				sel.select()
			} else {
				if (isNil(divRef.value)) return
				const selection = window.getSelection()
				// 光标选中
				const range = selection?.getRangeAt(0)
				// 删除光标选中
				if (range && !range?.collapsed) {
					// 不是同一位置 代表选择了内容 则先删除选择的内容
					range.deleteContents()
				}
				if (
					selection?.anchorNode &&
					[1, 3].includes(selection?.anchorNode.nodeType) &&
					selection?.anchorNode.parentNode === divRef.value
				) {
					if (range) {
						// 光标选中
						const textNode = range?.startContainer as any
						addRangeNode(textNode, spanHtml)
					}
				} else {
					divRef.value.innerHTML += imgSrc
					selection?.selectAllChildren(divRef.value) // 选择编辑器
					selection?.collapseToEnd() // 光标移动至最后
				}
			}
			onChange()
		}
		function addRangeNode(rangeNode: Node, dom: HTMLElement) {
			const node = rangeNode
			if (node && node.nodeType === Node.TEXT_NODE) {
				const content = node.textContent || ''
				const endIndex = getCursorIndex()
				const preSlice = replaceString(content.slice(0, endIndex), '')
				const restSlice = content.slice(endIndex)
				const parentNode = node.parentNode
				const nextNode = node.nextSibling
				const previousTextNode = new Text(preSlice)
				const nextTextNode = new Text('\u200b' + restSlice) // 添加 0 宽字符
				parentNode?.removeChild(node)
				// 插在文本框中
				if (nextNode) {
					parentNode?.insertBefore(previousTextNode, nextNode)
					parentNode?.insertBefore(dom, nextNode)
					parentNode?.insertBefore(nextTextNode, nextNode)
				} else {
					parentNode?.appendChild(previousTextNode)
					parentNode?.appendChild(dom)
					parentNode?.appendChild(nextTextNode)
				}
				setResetSelection(nextTextNode)
			}
		}
		// 获取光标位置
		function getCursorIndex() {
			const selection = window.getSelection()
			return selection?.focusOffset // 选择开始处 focusNode 的偏移量
		}
		// 获取节点
		function getRangeNode(): Node {
			const selection = window.getSelection()
			return selection?.focusNode as Node // 选择的结束节点
		}
		// 弹窗出现的位置
		function getRangeRect(): { x: number; y: number } {
			const selection = window.getSelection()
			const range = selection?.getRangeAt(0) // 是用于管理选择范围的通用对象
			const rect = range?.getClientRects()[0] // 择一些文本并将获得所选文本的范围
			const LINE_HEIGHT = 30
			return {
				x: rect?.x as number,
				y: (rect?.y as number) + LINE_HEIGHT,
			}
		}
		// 是否展示 @
		function showAt() {
			const node = getRangeNode()
			if (!node || node.nodeType !== Node.TEXT_NODE) return false
			const content = node.textContent || ''
			const regx = /@([^@\s]*)$/
			const match = regx.exec(content.slice(0, getCursorIndex()))
			return match && match.length === 2
		}
		// 获取 @ 用户
		function getAtUser() {
			const content = getRangeNode().textContent || ''
			const regx = /@([^@\s]*)$/
			const match = regx.exec(content.slice(0, getCursorIndex()))
			if (match && match.length === 2) {
				return match[1]
			}
			return undefined
		}
		// 创建标签
		function createAtButton(user: { nickname: any }) {
			return `<span contenteditable="false" style="display: inline-block;">
                        <span contenteditable="false" style="white-space: pre;">\u200b</span>
                        <span style="display: inline-block;" class="at-button">@${user.nickname}</span>
                        <span contenteditable="false" style="white-space: pre;">\u200b</span>
                    </span>&nbsp;`
		}
		function replaceString(raw: string, replacer: any) {
			return raw.replace(/@([^@\s]*)$/, replacer)
		}
		// 插入@标签
		function replaceAtUser(user: any) {
			const node = pageState.node
			const span = window.document.createElement('span')
			const atButton = createAtButton(user)
			span.innerHTML = atButton
			if (node && user) {
				addRangeNode(node, span)
			}
		}
		// 插入标签后隐藏选择框
		function handlePickUser(user: any) {
			replaceAtUser(user)
			pageState.user = user
			pageState.showDialog = false
		}
		function setResetSelection(nextTextNode: Text) {
			// 重置光标的位置
			const range = new Range()
			const selection = window.getSelection()
			range.setStart(nextTextNode, 0)
			range.setEnd(nextTextNode, 0)
			selection?.removeAllRanges()
			selection?.addRange(range)
		}
		//光标聚焦
		function setCursor(el: Node) {
			//el为光标需要定位的元素内
			const range = document.createRange()
			//返回用户当前的选区
			const sel = window.getSelection()
			range.setStart(el, 0)
			//使得选区(光标)开始与结束位置重叠
			range.collapse(true)
			//移除现有其他的选区
			sel?.removeAllRanges()
			//加入光标的选区
			sel?.addRange(range)
		}
		function onPaste(e: any) {
			e.preventDefault()
			let text
			const clp = (e.originalEvent || e).clipboardData
			if (clp === undefined || clp === null) {
				text = (window as any).clipboardData.getData('text') || ''
				if (text !== '') {
					if (window.getSelection as unknown as Selection) {
						const newNode = document.createElement('span')
						newNode.innerHTML = text(window as any)
							.getSelection()
							.getRangeAt(0)
							.insertNode(newNode)
					} else {
						;(document as any).selection.createRange().pasteHTML(text.replace(styleRel, '').replace(classRel, ''))
					}
				}
			} else {
				text = clp.getData('text/plain') || ''
				if (text !== '') {
					document.execCommand('insertHTML', false, text.replace(styleRel, '').replace(classRel, ''))
				}
			}
		}
		function handkeKeyUp() {
			if (showAt()) {
				const node = getRangeNode()
				const endIndex = getCursorIndex()
				pageState.node = node
				pageState.endIndex = endIndex
				pageState.position = getRangeRect()
				pageState.queryString = getAtUser() || ''
				pageState.showDialog = true
			} else {
				pageState.showDialog = false
			}
			onChange()
		}
		function handleKeyDown(e: { code: string; preventDefault: () => void }) {
			if (pageState.showDialog) {
				const { code } = e
				if (code === 'ArrowUp' || code === 'ArrowDown' || code === 'Enter') {
					e.preventDefault()
				}
			}
		}
		// 隐藏选择框
		function handleHide() {
			pageState.showDialog = false
		}
		function onSend() {
			if (!props.onSend) return
			props.onSend(pageState.queryString)
		}
		function onChange() {
			emit('update:content', divRef.value?.innerHTML as string)
			if (props.onChange) {
				props.onChange(divRef.value?.innerHTML as string)
			}
		}
		// 图片上传
		const fileRef = ref<HTMLInputElement>()
		const fileState = reactive({
			visible: false,
			src: '',
		})
		async function hdUpload() {
			const curFiles = fileRef.value?.files
			const reg = /.(png|jpg|gif|jpeg|webp)$/
			if (isNil(curFiles)) return
			const isJpgOrPng = reg.test(curFiles[0].name as string)
			if (!isJpgOrPng) {
				message.error('请上传图片!')
				return
			}
			const fr = new FileReader()
			// 资源的读取
			fr.readAsDataURL(curFiles[0])
			fr.onload = function () {
				// 将图片的src指向读取到的base64
				fileState.src = fr.result as string
				fileState.visible = true
			}
		}
		function openVideo() {
			Modal.confirm({
				title: '提示',
				content: '是否发起视频聊天',
				okText: '确认',
				cancelText: '取消',
				onOk: () => {
					emit('openVideo')
				},
			})
		}

		watch(
			() => props.content,
			(value) => {
				if (value) return
				divRef.value && (divRef.value.innerHTML = '')
				setCursor(divRef.value as HTMLElement)
			}
		)
		return () => (
			<div class="chatTool" style={{ height: `${props.height}px`, width: `${props.width}px` }}>
				<div class="toolbar">
					{props.showEmotion && (
						<FormEmotion class="emotion" onSendEmotion={onSendEmotion} direction={props.direction} />
					)}
					{props.showImg && (
						<a-tooltip placement="top" title="图片">
							<PictureOutlined onClick={() => fileRef.value?.click()} />
						</a-tooltip>
					)}
					{props.showVoice && (
						<a-tooltip placement="top" title="语音聊天">
							<PhoneOutlined />
						</a-tooltip>
					)}
					{props.showVideo && (
						<a-tooltip placement="top" title="视频聊天" onClick={openVideo}>
							<VideoCameraOutlined />
						</a-tooltip>
					)}
					{slots['toolbar'] && slots['toolbar']?.()}
				</div>
				<div
					class="editor"
					ref={divRef}
					contenteditable
					onKeyup={handkeKeyUp}
					onKeydown={handleKeyDown}
					placeholder={props.placeholder}
					onPaste={onPaste}
				></div>
				{props.showSend && (
					<div class="btn">
						<a-button type="primary" class="sendBtn" onClick={onSend}>
							发送
						</a-button>
					</div>
				)}
				{pageState.showDialog && (
					<AtDialog
						visible={pageState.showDialog}
						position={pageState.position}
						queryString={pageState.queryString}
						onPickUser={handlePickUser}
						onHide={handleHide}
						mockData={props.mockData}
					/>
				)}
				<input ref={fileRef} type="file" class="file" onChange={hdUpload} hidden />
			</div>
		)
	},
})
